package sf.r2dbc.binding;

import io.r2dbc.spi.Connection;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryMetadata;
import io.r2dbc.spi.ConnectionMetadata;
import sf.spring.util.LinkedCaseInsensitiveMap;

import java.util.Locale;
import java.util.Map;

/**
 * @see org.springframework.r2dbc.core.binding.BindMarkersFactoryResolver
 */
public class BuiltInBindMarkersFactoryUtils {

    private static final Map<String, BindMarkersFactory> BUILTIN =
            new LinkedCaseInsensitiveMap<>(Locale.ENGLISH);

    static {
        BUILTIN.put("H2", BindMarkersFactory.indexed("$", 1));
        BUILTIN.put("MariaDB", BindMarkersFactory.anonymous("?"));
        BUILTIN.put("Microsoft SQL Server", BindMarkersFactory.named("@", "P", 32,
                BuiltInBindMarkersFactoryUtils::filterBindMarker));
        BUILTIN.put("MySQL", BindMarkersFactory.anonymous("?"));
        BUILTIN.put("Oracle", BindMarkersFactory.named(":", "P", 32,
                BuiltInBindMarkersFactoryUtils::filterBindMarker));
        BUILTIN.put("PostgreSQL", BindMarkersFactory.indexed("$", 1));
    }


    public static BindMarkersFactory getBindMarkers(ConnectionFactory connectionFactory) {
        ConnectionFactoryMetadata metadata = connectionFactory.getMetadata();
        BindMarkersFactory r2dbcDialect = BUILTIN.get(metadata.getName());
        if (r2dbcDialect != null) {
            return r2dbcDialect;
        }
        for (String it : BUILTIN.keySet()) {
            if (metadata.getName().contains(it)) {
                return BUILTIN.get(it);
            }
        }
        return null;
    }

    public static BindMarkersFactory getBindMarkers(Connection connection) {

        ConnectionMetadata metadata = connection.getMetadata();
        BindMarkersFactory r2dbcDialect = BUILTIN.get(metadata.getDatabaseProductName());

        if (r2dbcDialect != null) {
            return r2dbcDialect;
        }

        return BUILTIN.keySet().stream() //
                .filter(it -> metadata.getDatabaseProductName().contains(it)) //
                .map(BUILTIN::get) //
                .findFirst().orElse(null);
    }

    private static String filterBindMarker(CharSequence input) {
        StringBuilder builder = new StringBuilder(input.length());
        for (int i = 0; i < input.length(); i++) {
            char ch = input.charAt(i);
            // ascii letter or digit
            if (Character.isLetterOrDigit(ch) && ch < 127) {
                builder.append(ch);
            }
        }
        if (builder.length() == 0) {
            return "";
        }
        return "_" + builder.toString();
    }
}